#!/usr/bin/python
import functools

# GUI imports
import wx

# library imports
import netio230alib

# Constants
portNum = 4

# Read port name file
fd = open("./port-names.txt", "r")
contents = fd.read()
fd.close()
portNames = {portNum: "All"}
for line in contents.split():
    if line.startswith("#"):
        continue
    key, value = line.split(":")
    portNames[int(key) - 1] = value.replace("_", " ")

# load the images
undefImage, onImage, offImage = (wx.Image(i, wx.BITMAP_TYPE_PNG) for i in ["grey.png", "green.png", "red.png"])

# Main class
class MyFrame(wx.Frame):
    ''' Main (and only) frame of the application. '''
    
    def OnAll(self, on, image, event, imageOff = None):
        global portNum
        for i in range(0, portNum):
            self.OnAction(i, on, image, event, imageOff = imageOff)

    def __init__(self, parent, portNames, id=-1, title='NETIO 230A Controller GUI',
                 pos=wx.DefaultPosition, size=(800, 600), style=wx.DEFAULT_FRAME_STYLE):
        wx.Frame.__init__(self, parent, id, title, pos, size, style) # call the constructor
        self.Bind(wx.EVT_CLOSE, self.OnClose)
        
        global portnum, undefImage, onImage, offImage
        self.SetBackgroundColour('grey')
        
        self._status = self.CreateStatusBar()
        self._status.SetStatusText("Application is starting...")
        self._stateImages = [] # the state image of the toggle button
        self._state = [] # the toggle (and port) state (0 or 1), initially undefined (-1)
        
        # Create application sizer (the "layout manager")
        sizer = wx.BoxSizer(wx.VERTICAL) # pack a title and the buttons vertically above each other
        
        # Create and add the title
        titleFont = wx.Font(18, wx.TELETYPE, wx.ITALIC, wx.NORMAL)
        title = wx.StaticText(self, -1, "NETIO 230A Controller GUI", (100, 10))
        title.SetForegroundColour('white')
        title.SetFont(titleFont)
        sizer.Add(title, 0, wx.BOTTOM) # 0 means non-stretching
        
        # Create and add the button panel
        buttonFont = wx.Font(16, wx.DEFAULT, wx.NORMAL, wx.BOLD)
        buttons = wx.Panel(self, -1) # the button panel for 4x3 buttons
        sizer.Add(buttons, 1, wx.EXPAND) # 1 means stretching
        
        # Fill the button panel
        bSizer = wx.GridSizer(rows = portNum + 1, cols = 3, vgap = 10, hgap = 10)

        for i in range(0, 5): # the NETIO 230A has 4 power sockets (1..4) stored at indexes 0..3
            if i in portNames:
                on = wx.Button(buttons, label = "Turn ON\n%s" % portNames[i])
            else:
                on = wx.Button(buttons, label = "Turn ON port %d" % (i + 1))
            on.SetFont(buttonFont)
            on.SetForegroundColour('green')
            bSizer.Add(on, 0, wx.EXPAND)
            if i < portNum:
                on.Bind(wx.EVT_LEFT_DOWN, functools.partial(self.OnAction, i, 1, onImage)) # 1 = on
            else:
                on.Bind(wx.EVT_LEFT_DOWN, functools.partial(self.OnAll, 1, onImage))
            
            toggle = wx.BitmapButton(buttons, -1, pos = wx.DefaultPosition)
            toggle.SetBitmapLabel(undefImage.ConvertToBitmap())
            self._stateImages.append(toggle)
            self._state.append(-1) # undefined
            bSizer.Add(toggle, 0, wx.EXPAND)
            if i < portNum:
                toggle.Bind(wx.EVT_LEFT_DOWN, functools.partial(self.OnAction, i, -1, onImage, imageOff = offImage))
            else:
                toggle.Bind(wx.EVT_LEFT_DOWN, functools.partial(self.OnAll, -1, onImage, imageOff = offImage))
            
            if i in portNames:
                off = wx.Button(buttons, label = "Turn OFF\n%s" % portNames[i])
            else:
                off = wx.Button(buttons, label = "Turn OFF port %d" % (i + 1))
            off.SetFont(buttonFont)
            off.SetForegroundColour('red')
            bSizer.Add(off, 0, wx.EXPAND)
            if i < portNum:
                off.Bind(wx.EVT_LEFT_DOWN, functools.partial(self.OnAction, i, 0, offImage)) # 0 = off
            else:
                off.Bind(wx.EVT_LEFT_DOWN, functools.partial(self.OnAll, 0, offImage))
            
        buttons.SetSizer(bSizer)
        self.SetSizer(sizer)
        self.Fit()
        self._status.SetStatusText("Application is ready.")
        self.Center() # center the frame on the active screen
            
    def OnAction(self, port, on, image, event, imageOff = None):
        ''' Activate action for the selected port '''
        global undefImage, onImage, offImage
        if on == -1:
            if self._state[port] in [-1,0]:
                self.OnAction(port, 1, image, event) # turn on if off (or try to turn on if undefined)
            else:
                self.OnAction(port, 0, imageOff, event) # turn off if on
            return
        if on:
            if self._state[port] == 1: return
            self._status.SetStatusText("Activating port %d..." % (port + 1))
        else:
            if self._state[port] == 0: return
            self._status.SetStatusText("Deactivating port %d..." % (port + 1))
        t = netio230alib.Netio230ALib()
        t.switchOnce(port + 1, on)
        if on:
            self._status.SetStatusText("Port %d was activated." % (port + 1))
        else:
            self._status.SetStatusText("Port %d was deactivated." % (port + 1))
        self._stateImages[port].SetBitmapLabel(image.ConvertToBitmap())
        self._state[port] = on
        if functools.reduce(lambda x,y: ((x > 0) + (y > 0) == 2), self._state[:portNum ], 1) > 0:
            self._stateImages[portNum].SetBitmapLabel(onImage.ConvertToBitmap())
        elif functools.reduce(lambda x,y: 1 - ((x == 0) and (y == 0)), self._state[:portNum ], 0) == 0:
            self._stateImages[portNum].SetBitmapLabel(offImage.ConvertToBitmap())
        else:
            self._stateImages[portNum].SetBitmapLabel(undefImage.ConvertToBitmap())
        event.Skip()
        return 

    def OnClose(self, event):
        ''' Close the application. '''
        self.Destroy()

class MyApp(wx.App):
    ''' Main app with a splash screen (taken from the web GUI of the switch.) '''
    def __init__(self, portNames):
        self._portNames = portNames
        wx.App.__init__(self)
    def OnInit(self):
        image = wx.Image("koukaam.png", wx.BITMAP_TYPE_PNG)
        bitmap = image.ConvertToBitmap()
        wx.SplashScreen(bitmap, wx.SPLASH_CENTER_ON_SCREEN | wx.SPLASH_TIMEOUT, 500, None, -1)
        wx.Yield()
        frame = MyFrame(None, self._portNames)
        frame.Show(True)
        self.SetTopWindow(frame)
        return True

# Start wxPython
app = MyApp(portNames)
app.MainLoop()
